﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.IO;

namespace BitcoinLibrary
{
    public class VersionMessage : Message
    {
        private static readonly long serialVersionUID = 7313594258967483180L;

        /**
         * A services flag that denotes whether the peer has a copy of the block chain or not.
         */
        public static readonly int NODE_NETWORK = 1;

        /** The version number of the protocol spoken. */
        public int clientVersion;
        /** Flags defining what is supported. Right now {@link #NODE_NETWORK} is the only flag defined. */
        public long localServices;
        /** What the other side believes the current time to be, in seconds. */
        public long time;
        /** What the other side believes the address of this program is. Not used. */
        public PeerAddress myAddr;
        /** What the other side believes their own address is. Not used. */
        public PeerAddress theirAddr;
        /**
         * An additional string that today the official client sets to the empty string. We treat it as something like an
         * HTTP User-Agent header.
         */
        public String subVer;
        /** How many blocks are in the chain, according to the other side. */
        public long bestHeight;

        public VersionMessage(NetworkParameters param, byte[] msg)
            : base(param, msg, 0)
        {
        }

        public VersionMessage(NetworkParameters param, int newBestHeight)
            : base(param)
        {
            clientVersion = NetworkParameters.PROTOCOL_VERSION;
            localServices = 0;
            time = Utils.ConvertToTimestamp(DateTime.Now);  //System.currentTimeMillis() / 1000;
            // Note that the official client doesn't do anything with these, and finding out your own external IP address
            // is kind of tricky anyway, so we just put nonsense here for now.
            try
            {
                //myAddr = new PeerAddress(IPAddress.get.g InetAddress.getLocalHost(), param.port, 0);
                myAddr = new PeerAddress(Dns.GetHostEntry(Dns.GetHostName()).AddressList.Where(ip => ip.AddressFamily == AddressFamily.InterNetwork).First(), param.port, 0);

                //theirAddr = new PeerAddress(InetAddress.getLocalHost(), params.port, 0);
                theirAddr = new PeerAddress(Dns.GetHostEntry(Dns.GetHostName()).AddressList.Where(ip => ip.AddressFamily == AddressFamily.InterNetwork).First(), param.port, 0);    // TODO: correct?
            }
            catch (Exception e)
            {
                throw e;  // Cannot happen.
            }
            subVer = "BitCoinJ 0.2";
            bestHeight = newBestHeight;
        }

        public override void parse()
        {
            clientVersion = (int)readUint32();
            localServices = (long)readUint64();
            time = (long)readUint64();
            myAddr = new PeerAddress(param, bytes, cursor, 0);
            cursor += myAddr.getMessageSize();
            theirAddr = new PeerAddress(param, bytes, cursor, 0);
            cursor += theirAddr.getMessageSize();
            // uint64 localHostNonce  (random data)
            // We don't care about the localhost nonce. It's used to detect connecting back to yourself in cases where
            // there are NATs and proxies in the way. However we don't listen for inbound connections so it's irrelevant.
            readUint64();
            //   string subVer  (currently "")
            subVer = readStr();
            //   int bestHeight (size of known block chain).
            bestHeight = readUint32();
        }

        public override void bitcoinSerializeToStream(Stream buf)
        {
            Utils.uint32ToByteStreamLE(clientVersion, buf);
            Utils.uint32ToByteStreamLE(localServices, buf);
            Utils.uint32ToByteStreamLE(localServices >> 32, buf);
            Utils.uint32ToByteStreamLE(time, buf);
            Utils.uint32ToByteStreamLE(time >> 32, buf);
            try
            {
                // My address.
                myAddr.bitcoinSerializeToStream(buf);
                // Their address.
                theirAddr.bitcoinSerializeToStream(buf);
            }
            catch (Exception e)
            {
                throw e;  // Can't happen.
            }

            // Next up is the "local host nonce", this is to detect the case of connecting
            // back to yourself. We don't care about this as we won't be accepting inbound
            // connections.
            Utils.uint32ToByteStreamLE(0, buf);
            Utils.uint32ToByteStreamLE(0, buf);
            // Now comes subVer.
            byte[] subVerBytes = System.Text.Encoding.UTF8.GetBytes(subVer);    //subVer.getBytes("UTF-8");
            var data = new VarInt(subVerBytes.Length).encode();
            buf.Write(data, 0, data.Length);
            buf.Write(subVerBytes, 0, subVerBytes.Length);
            // Size of known block chain.
            Utils.uint32ToByteStreamLE(bestHeight, buf);
        }

        /**
         * Returns true if the version message indicates the sender has a full copy of the block chain,
         * or if it's running in client mode (only has the headers).
         */
        public bool hasBlockChain()
        {
            return (localServices & NODE_NETWORK) == NODE_NETWORK;
        }
    }
}
